package jxtend.processor.reflect;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import jxtend.processor.exception.JxtendReflectionException;

public abstract class ReflectMethod<C> {

	protected final Method method;

	private ReflectMethod(Class<C> clazz, Class<?> returnType, String methodName, Class<?>... parameterTypes) {
		try {
			method = clazz.getDeclaredMethod(methodName, parameterTypes);
			method.setAccessible(true);
		} catch (SecurityException e) {
			throw new JxtendReflectionException(e);
		} catch (NoSuchMethodException e) {
			throw new JxtendReflectionException(e);
		}
	}

	public static <C> VoidMethod0Args<C> voidMethod(Class<C> clazz, String methodName) {
		return new VoidMethod0Args<C>(clazz, methodName);
	}

	public static <C, A1> VoidMethod1Args<C, A1> voidMethod(Class<C> clazz, String methodName,
			Class<A1> arg1Type) {
		return new VoidMethod1Args<C, A1>(clazz, methodName, arg1Type);
	}

	public static <C, A1, A2> VoidMethod2Args<C, A1, A2> voidMethod(Class<C> clazz, String methodName,
			Class<A1> arg1Type, Class<A2> arg2Type) {
		return new VoidMethod2Args<C, A1, A2>(clazz, methodName, arg1Type, arg2Type);
	}

	public static <C, A1, A2, A3> VoidMethod3Args<C, A1, A2, A3> voidMethod(Class<C> clazz, String methodName,
			Class<A1> arg1Type, Class<A2> arg2Type, Class<A3> arg3Type) {
		return new VoidMethod3Args<C, A1, A2, A3>(clazz, methodName, arg1Type, arg2Type, arg3Type);
	}

	public static <C, A1, A2, A3, A4> VoidMethod4Args<C, A1, A2, A3, A4> voidMethod(Class<C> clazz, String methodName,
			Class<A1> arg1Type, Class<A2> arg2Type, Class<A3> arg3Type, Class<A4> arg4Type) {
		return new VoidMethod4Args<C, A1, A2, A3, A4>(clazz, methodName, arg1Type, arg2Type, arg3Type, arg4Type);
	}

	private static abstract class VoidMethod<C> extends ReflectMethod<C> {
		public VoidMethod(Class<C> clazz, String methodName, Class<?>... parameterTypes) {
			super(clazz, Void.class, methodName, parameterTypes);
		}

		protected final void doInvoke(C obj, Object... args) {
			try {
				method.invoke(obj, args);
			} catch (IllegalArgumentException e) {
				throw new JxtendReflectionException(e);
			} catch (IllegalAccessException e) {
				throw new JxtendReflectionException(e);
			} catch (InvocationTargetException e) {
				throw new JxtendReflectionException(e);
			}
		}
	}
	public static final class VoidMethod0Args<C> extends VoidMethod<C> {
		VoidMethod0Args(Class<C> clazz, String methodName) {
			super(clazz, methodName);
		}

		public void invoke(C obj) { doInvoke(obj); }
	}

	public static final class VoidMethod1Args<C, A1> extends VoidMethod<C> {
		VoidMethod1Args(Class<C> clazz, String methodName, Class<A1> arg1Type) {
			super(clazz, methodName, arg1Type);
		}

		public void invoke(C obj, A1 arg1) { doInvoke(obj, arg1); }
	}

	public static final class VoidMethod2Args<C, A1, A2> extends VoidMethod<C> {
		VoidMethod2Args(Class<C> clazz, String methodName, Class<A1> arg1Type, Class<A2> arg2Type) {
			super(clazz, methodName, arg1Type, arg2Type);
		}

		public void invoke(C obj, A1 arg1, A2 arg2) { doInvoke(obj, arg1, arg2); }
	}

	public static final class VoidMethod3Args<C, A1, A2, A3> extends VoidMethod<C> {
		VoidMethod3Args(Class<C> clazz, String methodName, Class<A1> arg1Type, Class<A2> arg2Type, Class<A3> arg3Type) {
			super(clazz, methodName, arg1Type, arg2Type, arg3Type);
		}

		public void invoke(C obj, A1 arg1, A2 arg2, A3 arg3) { doInvoke(obj, arg1, arg2, arg3); }
	}

	public static final class VoidMethod4Args<C, A1, A2, A3, A4> extends VoidMethod<C> {
		VoidMethod4Args(Class<C> clazz, String methodName,
				Class<A1> arg1Type, Class<A2> arg2Type, Class<A3> arg3Type, Class<A4> arg4Type) {
			super(clazz, methodName, arg1Type, arg2Type, arg3Type, arg4Type);
		}

		public void invoke(C obj, A1 arg1, A2 arg2, A3 arg3, A4 arg4) { doInvoke(obj, arg1, arg2, arg3, arg4); }
	}

	public static <C, R> ResultMethod0Args<C, R> method(Class<C> clazz, Class<R> returnType, String methodName) {
		return new ResultMethod0Args<C, R>(clazz, returnType, methodName);
	}

	public static <C, R, A1> ResultMethod1Args<C, R, A1> method(Class<C> clazz, Class<R> returnType,
			String methodName, Class<A1> arg1Type) {
		return new ResultMethod1Args<C, R, A1>(clazz, returnType, methodName, arg1Type);
	}

	public static <C, R, A1, A2> ResultMethod2Args<C, R, A1, A2> method(Class<C> clazz, Class<R> returnType,
			String methodName, Class<A1> arg1Type, Class<A2> arg2Type) {
		return new ResultMethod2Args<C, R, A1, A2>(clazz, returnType, methodName, arg1Type, arg2Type);
	}

	public static <C, R, A1, A2, A3> ResultMethod3Args<C, R, A1, A2, A3> method(Class<C> clazz, Class<R> returnType,
			String methodName, Class<A1> arg1Type, Class<A2> arg2Type, Class<A3> arg3Type) {
		return new ResultMethod3Args<C, R, A1, A2, A3>(clazz, returnType, methodName, arg1Type, arg2Type, arg3Type);
	}

	public static <C, R, A1, A2, A3, A4> ResultMethod4Args<C, R, A1, A2, A3, A4> method(
			Class<C> clazz, Class<R> returnType, String methodName,
			Class<A1> arg1Type, Class<A2> arg2Type, Class<A3> arg3Type, Class<A4> arg4Type) {
		return new ResultMethod4Args<C, R, A1, A2, A3, A4>(clazz, returnType,
				methodName, arg1Type, arg2Type, arg3Type, arg4Type);
	}

	private static abstract class ResultMethod<C, R> extends ReflectMethod<C> {
		public ResultMethod(Class<C> clazz, Class<R> returnType, String methodName, Class<?>... parameterTypes) {
			super(clazz, Void.class, methodName, parameterTypes);
		}

		@SuppressWarnings("unchecked")
		protected final R doInvoke(C obj, Object... args) {
			try {
				return (R) method.invoke(obj, args);
			} catch (IllegalArgumentException e) {
				throw new JxtendReflectionException(e);
			} catch (IllegalAccessException e) {
				throw new JxtendReflectionException(e);
			} catch (InvocationTargetException e) {
				throw new JxtendReflectionException(e);
			}
		}
	}
	public static final class ResultMethod0Args<C, R> extends ResultMethod<C, R> {
		ResultMethod0Args(Class<C> clazz, Class<R> returnType, String methodName) {
			super(clazz, returnType, methodName);
		}

		public R invoke(C obj) { return doInvoke(obj); }
	}

	public static final class ResultMethod1Args<C, R, A1> extends ResultMethod<C, R> {
		ResultMethod1Args(Class<C> clazz, Class<R> returnType, String methodName, Class<A1> arg1Type) {
			super(clazz, returnType, methodName, arg1Type);
		}

		public R invoke(C obj, A1 arg1) { return doInvoke(obj, arg1); }
	}

	public static final class ResultMethod2Args<C, R, A1, A2> extends ResultMethod<C, R> {
		ResultMethod2Args(Class<C> clazz, Class<R> returnType, String methodName,
				Class<A1> arg1Type, Class<A2> arg2Type) {
			super(clazz, returnType, methodName, arg1Type, arg2Type);
		}

		public R invoke(C obj, A1 arg1, A2 arg2) { return doInvoke(obj, arg1, arg2); }
	}

	public static final class ResultMethod3Args<C, R, A1, A2, A3> extends ResultMethod<C, R> {
		ResultMethod3Args(Class<C> clazz, Class<R> returnType, String methodName,
				Class<A1> arg1Type, Class<A2> arg2Type, Class<A3> arg3Type) {
			super(clazz, returnType, methodName, arg1Type, arg2Type, arg3Type);
		}

		public R invoke(C obj, A1 arg1, A2 arg2, A3 arg3) { return doInvoke(obj, arg1, arg2, arg3); }
	}

	public static final class ResultMethod4Args<C, R, A1, A2, A3, A4> extends ResultMethod<C, R> {
		ResultMethod4Args(Class<C> clazz, Class<R> returnType, String methodName,
				Class<A1> arg1Type, Class<A2> arg2Type, Class<A3> arg3Type, Class<A4> arg4Type) {
			super(clazz, returnType, methodName, arg1Type, arg2Type, arg3Type, arg4Type);
		}

		public R invoke(C obj, A1 arg1, A2 arg2, A3 arg3, A4 arg4) { return doInvoke(obj, arg1, arg2, arg3, arg4); }
	}
}
